package com.sun.stockexchangesimulate.simulate;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.sun.stockexchangesimulate.domain.*;
import com.sun.stockexchangesimulate.mapper.*;
import com.sun.stockexchangesimulate.service.DealService;
import com.sun.stockexchangesimulate.service.InvestorService;
import com.sun.stockexchangesimulate.service.StockPriceService;
import com.sun.stockexchangesimulate.service.StockService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Haoran Sun
 * @description 用于每段时间投资者创建订单后进行交易撮合系统采用连续订单撮合算法进行撮合
 * @since 2024/10/26
 */

@Service
public class SystemRun {


    public static final Random RANDOM = new Random();
    @Resource
    private TimeSimulator timeSimulator;

    @Resource
    private StockHoldingMapper stockHoldingMapper;
    @Resource
    private StockOrderMapper stockOrderMapper;
    @Resource
    private InvestorService investorService;
    @Resource
    private StockPriceIntradayMapper stockPriceIntradayMapper;
    @Resource
    private StockPriceMapper stockPriceMapper;
    @Resource
    private StockPriceService stockPriceService;
    @Resource
    private InvestorMapper investorMapper;
    @Resource
    private DealService dealService;
    @Resource
    private StockService stockService;

    /**
     * 单日的股票市场模拟运行
     */
    public void run() {
        int loop = 13;
        for (int i = 0; i < loop; i++) {
            // 1、投资者根据自身信息进行交易
            // 即创建订单
            simulateInvestor();
            // 2、订单撮合
            orderMatching();
            // 3、这里未交易的订单会留存在系统当中，等待下一轮撮合。
            // TODO 订单簿，用户取消订单
            // 4、模拟时间推进半个小时
            timeSimulator.advanceByHalfHour();
        }
        // 每日信息汇总 --- 即跟新股票的price表
        dealPrice();
        // 每日订单过期处理
        expireDeal();

    }

    /**
     * 订单撮合
     *
     * @1 将所有订单按照价格优先、时间优先的原则进行排序。对于买入订单，价格高的优先；对于卖出订单，价格低的优先。在价格相同的情况下，时间早的订单优先。
     * @2 从最高优先级的买卖订单对开始进行撮合。如果买入订单的价格大于等于卖出订单的价格，则进行成交
     */
    public void orderMatching() {
        // 1、获取所有(未成交和部分成交的)订单
        List<StockOrder> stockOrders = stockOrderMapper.selectList(new LambdaQueryWrapper<StockOrder>()
                .in(StockOrder::getStatus, "0", "2"));
        // 2、处理订单
        // 2.1、先按按买卖分组再按股票分组
        Map<String, Map<String, List<StockOrder>>> collect = stockOrders.stream()
                .collect(Collectors.groupingBy(
                        StockOrder::getType,
                        Collectors.groupingBy(StockOrder::getStockCode)));


        // 2.2、买入订单，价格高的优先
        Map<String, List<StockOrder>> buyOrders = collect.get("0");
        // 价格优先、时间优先的原则对各组中的StockOrder进行排序
        buyOrders.forEach((key, value) -> value
                .sort(Comparator.comparing(StockOrder::getPrice)
                        .reversed()
                        .thenComparing(StockOrder::getCreateDate)
                ));
        // 2.3、卖出订单，价格低的优先
        Map<String, List<StockOrder>> sellOrders = collect.get("1");
        sellOrders.forEach((key, value) -> value
                .sort(Comparator.comparing(StockOrder::getPrice)
                        .thenComparing(StockOrder::getCreateDate)
                ));

        // 3、撮合
        buyOrders.keySet().forEach(
                key -> {
                    List<StockOrder> buys = buyOrders.get(key);
                    int i = 0;
                    List<StockOrder> sells = sellOrders.get(key);
                    int j = 0;

                    // 3.1、如果卖出订单为空，则跳过
                    if (sells != null && !sells.isEmpty() && buys != null && !buys.isEmpty()) {

                        int totalVolume = 0;
                        BigDecimal price = null;

                        // 买卖订单进行匹配，直到无订单可以匹配为止
                        while (i < buys.size() && j < sells.size()) {
                            StockOrder buy = buys.get(i);
                            StockOrder sell = sells.get(j);
                            if (buy.getPrice().compareTo(sell.getPrice()) >= 0 && sell.getRemainingQuantity() > 0 && buy.getRemainingQuantity() > 0) {
                                // 买入订单价格大于等于卖出订单价格，进行成交
                                int num = Math.min(buy.getRemainingQuantity(), sell.getRemainingQuantity());
                                // 1、修改订单
                                if (buy.getRemainingQuantity() >= sell.getRemainingQuantity()) {
                                    buy.setStatus("2");
                                    buy.setRemainingQuantity(buy.getRemainingQuantity() - sell.getRemainingQuantity());
                                    stockOrderMapper.updateById(buy);
                                    sell.setStatus("1");
                                    sell.setRemainingQuantity(0);
                                    stockOrderMapper.updateById(sell);
                                    j++;
                                } else {
                                    // 买入订单数量小于卖出订单数量，修改买入订单数量，卖出订单数量为0，买入订单状态为部分成交
                                    buy.setRemainingQuantity(0);
                                    buy.setStatus("1");
                                    stockOrderMapper.updateById(buy);
                                    sell.setRemainingQuantity(sell.getRemainingQuantity() - buy.getRemainingQuantity());
                                    sell.setStatus("2");
                                    stockOrderMapper.updateById(sell);
                                    i++;
                                }
                                // 2、修改持仓
                                stockHoldingMapper.updateHolding(StockHolding.builder()
                                        .investorId(buy.getInvestorId())
                                        .quantity(num)
                                        .stockCode(buy.getStockCode())
                                        .build());
                                stockHoldingMapper.updateHolding(StockHolding.builder()
                                        .quantity(-num)
                                        .investorId(sell.getInvestorId())
                                        .stockCode(sell.getStockCode())
                                        .build());
                                // 3、更新账户余额
                                BigDecimal sellPrice = sell.getPrice().multiply(new BigDecimal(sell.getRemainingQuantity()));
                                // TODO 这里有问题，更新可用余额的问题，卖出行为会使得投资者的可用余额增加
                                investorService.updateAccount(buy.getInvestorId(), sell.getInvestorId(), sellPrice);

                                // 4、更新交易记录
                                dealService.save(Deal.builder()
                                        .buyId(buy.getId())
                                        .sellId(sell.getId())
                                        .stockCode(buy.getStockCode())
                                        .price(sell.getPrice())
                                        .quantity(num)
                                        .date(timeSimulator.getCurrentLocalDateTime())
                                        .build());
                                // 5、统计信息-- 成交量、交易价格
                                totalVolume += num;
                                price =  sell.getPrice();
                            } else {
                                break;
                            }
                        }

                        // 统计信息入库
//                        if (totalVolume == 0) {// 成交量为零价格不变
//                            if (timeSimulator.getCurrentTime().isBefore(LocalTime.of(9, 30))) {
//                                price = stockPriceMapper.selectOne(new LambdaQueryWrapper<StockPrice>()
//                                                .eq(StockPrice::getStockId, key)
//                                                .orderByDesc(StockPrice::getDate)
//                                                .last("LIMIT 1"))
//                                        .getClosePrice();
//                            } else {
//                                price = stockPriceIntradayMapper.selectOne(new LambdaQueryWrapper<StockPriceIntraday>()
//                                        .eq(StockPriceIntraday::getStockCode, key)
//                                        .le(StockPriceIntraday::getDate, timeSimulator.getCurrentLocalDate())
//                                        .ge(StockPriceIntraday::getDate, timeSimulator.getCurrentLocalDate().atTime(8, 0))
//                                        .orderByDesc(StockPriceIntraday::getTime)
//                                        .last("LIMIT 1")
//                                ).getPrice();
//                            }
//                        }
                        // 这里总成交量不等于零才能入库，等于零的再统计时采用上一个时段的收盘价或价格
                        if (totalVolume != 0){
                            stockPriceIntradayMapper.insert(StockPriceIntraday.builder()
                                    .date(timeSimulator.getCurrentLocalDate())
                                    .time(timeSimulator.getCurrentTime())
                                    .stockCode(key)
                                    .volume((long) totalVolume)
                                    .price(price)
                                    .build());
                        }
                    }
                }
        );
    }

    /**
     * 模拟投资者进行投资
     * 投资者的一轮模拟投资
     * <p>
     * <p>
     * <p>
     * 1、卖买选择
     * <p>
     * 根据自己的现有资金数量进行，
     * <p>
     * 若资金短缺即小于某个值，则进行买股票，数量随机，
     * <p>
     * 否则，本轮投资者随机进行买卖。
     * <p>
     * 2、股票选择
     * <p>
     * 卖：
     * <p>
     * 自己拥有的股票随机选择一支
     * <p>
     * 买：
     * <p>
     * 随机选择
     * <p>
     * 3、数量选择
     * <p>
     * 买：
     * <p>
     * 需要购买的现金小于自己的可用现金，
     * <p>
     * 卖：
     * <p>
     * 小于拥有的股票数量
     */
    public void simulateInvestor() {

        List<Investor> list = investorService.list();
        list.forEach(investor -> {
            // 模拟投资
            //一、 买卖选择
            // flag1 0：卖出，1：买入
            int flag1 = 0;
            if (investor.getAvailableCash().compareTo(BigDecimal.valueOf(10000)) > 0) {
                // 可用现金小于一万进行卖出操作,大于一万随机进行买入或卖出操作
                flag1 = RANDOM.nextInt(2);
            }
            if (flag1 == 0) {
                // 卖出操作
                List<StockHolding> stockHoldings = stockHoldingMapper.selectList(new LambdaQueryWrapper<StockHolding>().eq(StockHolding::getInvestorId, investor.getId()));
                // 随机选择一支拥有的股票
                StockHolding stockHolding = stockHoldings.get(RANDOM.nextInt(stockHoldings.size()));
                int quantity = (int) (stockHolding.getQuantity() * RANDOM.nextDouble());
                StockOrder stockOrder = StockOrder.builder()
                        .investorId(investor.getId())
                        .stockCode(stockHolding.getStockCode())
                        .price(stockPriceMapper.selectOne(new LambdaQueryWrapper<StockPrice>()
                                        .eq(StockPrice::getStockId, stockHolding.getStockCode())
                                        .orderByDesc(StockPrice::getDate)
                                        .last("LIMIT 1")//最近一天的价格
                                )
                                .getClosePrice().multiply(BigDecimal.valueOf(0.5 + RANDOM.nextDouble())))
                        .quantity(quantity)
                        .type("1")
                        .createDate(timeSimulator.getRandomCurrentDateTime())
                        .status("0")
                        .remainingQuantity(quantity)
                        .build();
                stockOrderMapper.insert(stockOrder);
            }

            if (flag1 == 1) {
                // 买入操作
                // 随机选择一支股票
                String stockCode = String.format("%02d", RANDOM.nextInt(38) + 1);
                BigDecimal aCash = investor.getAvailableCash().multiply(BigDecimal.valueOf(0.05 * (1 + RANDOM.nextDouble())));
                StockPrice stockPrice = stockPriceMapper.selectOne(new LambdaQueryWrapper<StockPrice>().eq(StockPrice::getStockId, stockCode)
                        .orderByDesc(StockPrice::getDate)
                        .last("LIMIT 1")//最近一天的价格
                );
                BigDecimal price = stockPrice.getClosePrice().multiply(BigDecimal.valueOf(0.5 + RANDOM.nextDouble()));
                int quantity = aCash.divide(price, 0, RoundingMode.DOWN).intValue();
                // 更新账户可用余额
                investorMapper.update(
                        new LambdaUpdateWrapper<Investor>().eq(Investor::getId, investor.getId())
                                .set(Investor::getAvailableCash, investor.getAvailableCash().subtract(price.multiply(BigDecimal.valueOf(quantity))))
                );
                stockOrderMapper.insert(StockOrder.builder()
                        .investorId(investor.getId())
                        .stockCode(stockCode)
                        .price(price)
                        .quantity(quantity)
                        .type("0")
                        .createDate(timeSimulator.getRandomCurrentDateTime())
                        .status("0")
                        .remainingQuantity(quantity)
                        .build()
                );
            }
        });
    }

    /**
     * 每日价格处理
     * <p>
     * 每只股票的
     * <p>
     * 1、开盘价
     * <p>
     * 每日第一批订单中,该股票成交量最多的价格
     * <p>
     * 2、收盘价
     * <p>
     * 每日最后一批订单中,成交量最多的价格
     * <p>
     * 3、最高价
     * <p>
     * 成交订单中价格最高的
     * <p>
     * 4、最低价
     * <p>
     * 成交订单中价格最低的
     * <p>
     * 5、成交量
     * <p>
     * 成交订单的数量
     */
    public void dealPrice() {
        // 当天没有交易的股票需要插入一条记录
        List<String> stockCodes = stockService.list().stream().map(Stock::getCode).collect(Collectors.toList());

        // 获取当天交易记录
        List<Deal> list = dealService.list(new LambdaQueryWrapper<Deal>().eq(Deal::getDate, timeSimulator.getCurrentLocalDateTime()));
        Map<String, List<Deal>> map = list
                .stream()
                .collect(Collectors
                        .groupingBy(Deal::getStockCode
                                , Collectors.collectingAndThen(Collectors.toList(),
                                        deals -> deals.stream()
                                                .sorted(Comparator.comparing(Deal::getDate)).collect(Collectors.toList()))
                        ));
        map.forEach((k, v) -> {
            stockCodes.remove(k);
            // 开盘价、关盘价
            LocalDateTime firstDate = v.get(0).getDate();
            LocalDateTime lastDate = v.get(v.size() - 1).getDate();
            List<Deal> firstDeals = v.stream().filter(deal -> deal.getDate().equals(firstDate)).collect(Collectors.toList());
            List<Deal> lastDeals = v.stream().filter(deal -> deal.getDate().equals(lastDate)).collect(Collectors.toList());
            // 先对数据处理，将价格相同的deal合并，方便后续查找交易最多的价格
            List<Deal> mergedFirstDeals = mergeDealsByPrice(firstDeals);
            List<Deal> mergedLastDeals = mergeDealsByPrice(lastDeals);
            BigDecimal openPrice = mergedFirstDeals.stream()
                    .max(Comparator.comparing(Deal::getQuantity))
                    .map(Deal::getPrice).orElse(null);
            BigDecimal closePrice = mergedLastDeals.stream()
                    .max(Comparator.comparing(Deal::getQuantity))
                    .map(Deal::getPrice).orElse(null);
            // 最高价、最低价
            BigDecimal maxPrice = v.stream().max(Comparator.comparing(Deal::getPrice))
                    .map(Deal::getPrice).orElse(null);
            BigDecimal minPrice = v.stream().min(Comparator.comparing(Deal::getPrice))
                    .map(Deal::getPrice).orElse(null);
            // 成交量
            Integer quantity = v.stream().map(Deal::getQuantity).reduce(Integer::sum).orElse(0);
            stockPriceMapper.insert(
                    StockPrice.builder()
                            .openPrice(openPrice)
                            .closePrice(closePrice)
                            .highPrice(maxPrice)
                            .lowPrice(minPrice)
                            .volume(quantity)
                            .stockId(k)
                            .date(timeSimulator.getCurrentLocalDate())
                            .build());
        });

        if (!stockCodes.isEmpty()) {
            List<Stock> stocks = stockService.list(new LambdaQueryWrapper<Stock>().in(Stock::getCode, stockCodes));
            List<StockPrice> stockPrices = stockPriceMapper.selectList(new LambdaQueryWrapper<StockPrice>().in(StockPrice::getStockId, stockCodes))
                    .stream()
                    .map(stockPrice ->
                            StockPrice.builder()
                                    .openPrice(stockPrice.getClosePrice())
                                    .closePrice(stockPrice.getClosePrice())
                                    .highPrice(stockPrice.getClosePrice())
                                    .lowPrice(stockPrice.getClosePrice())
                                    .volume(0)
                                    .date(timeSimulator.getCurrentLocalDate())
                                    .stockId(stockPrice.getStockId())
                                    .build()
                    ).collect(Collectors.toList());
            stockPriceService.saveBatch(stockPrices);
        }

    }

    /**
     * 过期订单处理
     */
    public void expireDeal() {
        //1、找出所有部分成交和未成交的买入订单
        List<StockOrder> stockOrders = stockOrderMapper.selectList(new LambdaQueryWrapper<StockOrder>()
                .nested(i -> i.eq(StockOrder::getStatus, "2")
                        .or()
                        .eq(StockOrder::getStatus, "0"))
                .eq(StockOrder::getType, "0"));
        //2、计算价格并更新账户余额
        stockOrders.forEach(stockOrder -> {
            BigDecimal price = stockOrder.getPrice();
            Integer quantity = stockOrder.getRemainingQuantity();
            Integer investorId = stockOrder.getInvestorId();
            //3、更新账户余额
            investorService.updateAvailableAmount(investorId, price.multiply(BigDecimal.valueOf(quantity)));
        });
        //3、将所有未成交的订单改为已取消
        stockOrderMapper.update(new LambdaUpdateWrapper<StockOrder>()
                .nested(i -> i.eq(StockOrder::getStatus, "2")
                        .or()
                        .eq(StockOrder::getStatus, "0"))
                .set(StockOrder::getStatus, "3")
        );

        // 4、更新投资者可用金额
        investorService.update(new LambdaUpdateWrapper<Investor>().setSql("available_cash = cash"));
    }

    private static List<Deal> mergeDealsByPrice(List<Deal> deals) {
        return new ArrayList<>(deals.stream()
                // 利用map来进行合并
                .collect(Collectors.toMap(
                        Deal::getPrice,
                        deal -> deal,
                        (existing, replacement) -> {
                            existing.setQuantity(existing.getQuantity() + replacement.getQuantity());
                            return existing;
                        }
                ))
                .values());
    }
}
